package concurrecy;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

class Waiter1 implements Runnable {

    private Restaurant1 restaurant1;
    private int i;

    public Waiter1(Restaurant1 restaurant1, int i) {
        this.restaurant1 = restaurant1;
        this.i = i;
    }

    @Override
    public String toString() {
        return "Waiter" + i;
    }

    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                Meal meal = null;
                synchronized (this) {
                    do {
                        meal = restaurant1.getMeal();
                        if(meal == null) {
                            if(restaurant1.soldOut)
                                restaurant1.closeDoor();
                            System.out.println(this + " waiting ...");
                            restaurant1.notifyChef();
                            wait();
                        }
                    } while (meal == null);
                }
                System.out.println(this + " token " + meal);
                TimeUnit.SECONDS.sleep(3); // 上菜需要3秒钟
            }
        } catch(InterruptedException e) {
            System.out.println(this + " interrupted");
        }
        System.out.println(this + " go home");
    }
}

class Chef1 implements Runnable {

    private Restaurant1 restaurant1;
    private int i;
    private static volatile AtomicInteger count = new AtomicInteger(0);

    public Chef1(Restaurant1 restaurant1, int i) {
        this.restaurant1 = restaurant1;
        this.i = i;
    }

    @Override
    public String toString() {
        return "Chef" + i;
    }

    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                if(count.get() >= Restaurant1.TOTAL_FOOD) {
                    restaurant1.soldOut = true;
                    while(true) {
                        synchronized (this) {
                            wait();
                            System.out.println(this + " out of food, nothing to do");
                        }
                    }
                }
                Meal meal = new Meal(count.incrementAndGet());
                System.out.println(this + " make " + meal);
                synchronized (this) {
                    while(!restaurant1.putMeal(meal)) {
                        restaurant1.notifyWaiter();
                        System.out.println(this + " waiting ...");
                        wait();
                    }
                }
                restaurant1.notifyWaiter();

                TimeUnit.MILLISECONDS.sleep(500); // 休息一下
            }
        } catch(InterruptedException e) {
            System.out.println(this + " interrupted!");
        }
        System.out.println(this + " go home");
    }
}

class Restaurant1 {

    public final static int TOTAL_FOOD = 50;
    public final static int NUM_CHEF = 5;
    public final static int NUM_WAITER = 8;


    public volatile boolean soldOut = false;

    private final LinkedList<Meal> meals = new LinkedList<>();

    private List<Chef1> chef1s = new ArrayList<>();
    private List<Waiter1> waiter1s = new ArrayList<>();

    private ExecutorService service = Executors.newCachedThreadPool();

    public Restaurant1() {
        synchronized (chef1s) {
            for (int i = 0; i < NUM_CHEF; i++) {
                Chef1 chef1 = new Chef1(this, i);
                chef1s.add(chef1);
                service.execute(chef1);
            }
        }
        synchronized (waiter1s) {
            for (int i = 0; i < NUM_WAITER; i++) {
                Waiter1 waiter1 = new Waiter1(this, i);
                waiter1s.add(waiter1);
                service.execute(waiter1);
            }
        }
    }

    public void closeDoor() {
        service.shutdownNow();
    }

    public void notifyChef() {
        System.out.println("notify chefs");
        synchronized (chef1s) {
            for (Chef1 chef1 : chef1s) {
                synchronized (chef1) {
                    chef1.notifyAll();
                }
            }
        }
    }

    public void notifyWaiter() {
        System.out.println("notify waiters");
        synchronized (waiter1s) {
            for (Waiter1 waiter1 : waiter1s) {
                synchronized (waiter1) {
                    waiter1.notifyAll();
                }
            }
        }
    }

    public Meal getMeal() {
        synchronized (meals) {
            return meals.isEmpty() ? null : meals.removeFirst();
        }
    }

    public boolean putMeal(Meal meal) {
        synchronized (meals) {
            if(meals.size() >= 16)
                return false;
            else {
                meals.add(meal);
                System.out.println("put meal " + meal);
                return true;
            }
        }
    }
}

/**
 * 多个生产者消费者
 */
public class RestaurantDemo1 {
    public static void main(String[] args) {
        new Restaurant1();
    }
}
